Skip to content

chore: refactor permission rules and add additional validation#7844

Merged
jeffsmale90 merged 18 commits intomainfrom
chore/gator_permissions_decoding_adversarial_tests
Mar 6, 2026
Merged

chore: refactor permission rules and add additional validation#7844
jeffsmale90 merged 18 commits intomainfrom
chore/gator_permissions_decoding_adversarial_tests

Conversation

@jeffsmale90
Copy link
Contributor

@jeffsmale90 jeffsmale90 commented Feb 4, 2026

Explanation

It's critical that the GatorPermissionController's Permission decoding logic is strict, and will not decode EIP-712 payload to a permission unless the payload exactly meets the expectations of that permission.

This PR refactors the permission rules to be more self contained, and self describing. This allows each permission type's decoding rules to be more thoroughly tested in isolation. Validation and decoding logic is combined, as decoding is an implicit part of the validation step.

This PR also adds explicit validation of the "implicit" caveats for each permission type (valueLte for ERC20 permissions, exactCalldata for native token permissions), where previously we were ensuring that they caveats exist, but not validating their terms.

References

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

High Risk
Changes the core permission-type identification and caveat decoding/validation path; this could cause previously accepted permission contexts to be rejected or misclassified if rule matching/term checks are incorrect.

Overview
Refactors permission decoding to be rule-driven and stricter. GatorPermissionsController.decodePermissionFromPermissionContextForOrigin now builds per-chain permission rules and selects a single matching rule via findRuleWithMatchingCaveatAddresses, then validates+decodes caveat terms through rule.validateAndDecodePermission (replacing identifyPermissionByEnforcers + getPermissionDataAndExpiry).

Adds explicit caveat-term validation per permission type. New rule modules validate enforcer term structure and constraints (e.g., ExactCalldataEnforcer must be 0x, ValueLteEnforcer must be zero, byte-length checks, positive periodAmount/periodDuration/startTime, ERC20 tokenAddress hex validation, and stream maxAmount > initialAmount). The test suite is reorganized to test rules in isolation and adds adversarial/negative cases; changelog updated accordingly.

Written by Cursor Bugbot for commit 79be687. This will update automatically on new commits. Configure here.

@jeffsmale90 jeffsmale90 changed the title Add adversarial tests for permission decoding. Add additional validation for token permission types. chore: add adversarial tests for permission decoding. Add additional validation for token permission types. Feb 10, 2026
@jeffsmale90 jeffsmale90 force-pushed the chore/gator_permissions_decoding_adversarial_tests branch from 4cd5553 to df72ecd Compare March 2, 2026 03:27
@jeffsmale90 jeffsmale90 changed the title chore: add adversarial tests for permission decoding. Add additional validation for token permission types. chore: refactor permission rules and add additional validation Mar 2, 2026
@jeffsmale90 jeffsmale90 force-pushed the chore/gator_permissions_decoding_adversarial_tests branch 3 times, most recently from a9450f5 to 8700f23 Compare March 2, 2026 21:34
@jeffsmale90 jeffsmale90 marked this pull request as ready for review March 2, 2026 21:34
@jeffsmale90 jeffsmale90 requested a review from a team as a code owner March 2, 2026 21:34
@jeffsmale90 jeffsmale90 force-pushed the chore/gator_permissions_decoding_adversarial_tests branch from 52df231 to e1cb91e Compare March 2, 2026 23:29
@jeffsmale90 jeffsmale90 requested a review from a team as a code owner March 3, 2026 01:17
});

it('rejects when startTime is 0', () => {
const tokenAddress = '0xdddddddddddddddddddddddddddddddddddddddd' as Hex;
Copy link

@mj-kiwi mj-kiwi Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the valid 20-byte addresses should be 40 hex chars.

Suggested change
const tokenAddress = '0xdddddddddddddddddddddddddddddddddddddddd' as Hex;
const tokenAddress = '0xdddddddddddddddddddddddddddddddddddddd' as Hex;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is 40 hex chars:

> "0xdddddddddddddddddddddddddddddddddddddddd".length
42

"0x" followed by 40 "d"s is 42 chars

});

it('rejects when startTime is 0', () => {
const tokenAddress = '0xdddddddddddddddddddddddddddddddddddddddd' as Hex;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const tokenAddress = '0xdddddddddddddddddddddddddddddddddddddddd' as Hex;
const tokenAddress = '0xdddddddddddddddddddddddddddddddddddddd' as Hex;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lol, I counted those ds very carefully!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the existing string if 42 chars, which is 20 bytes, which is correct.

I added validation to the erc20 permission decoders to ensure that the token addresses are the correct length.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I didn't, because we can't validate individual component lengths (the bytes are concatenated), but we do have terms length validation for each permission type.

splitHex(terms, [20, 32, 32, 32]);
const periodDuration = hexToNumber(periodDurationRaw);
const startTime = hexToNumber(startTimeRaw);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add periodAmount > 0 check here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes!

@jeffsmale90 jeffsmale90 force-pushed the chore/gator_permissions_decoding_adversarial_tests branch from 9990e4f to 5ff2c48 Compare March 4, 2026 01:30
@jeffsmale90 jeffsmale90 requested a review from mj-kiwi March 4, 2026 01:30
…ion to ensure that permission data invariants are not violated.
…ype is self-describing and can be more easily tested in isolation. Add validation and test coverage for each permission type.
Plus minor changes:
- Remove redundant amendment to ChecksumCaveat type
- Remove unused ValidateDecodedPermission type
- Fixes controller tests that expected the controller to self-report GatorPermissionsSnap id
- Make decode functions internal, and rename to align with public interface
…e ChecksumEnforcersByChainId type rather than explicitly declaring a new type
- use metamask/utils isHexAddress instead of regex to validate addresses
- use hexToBigInt to validate tokenPeriod instead of hexToNumber
@jeffsmale90 jeffsmale90 force-pushed the chore/gator_permissions_decoding_adversarial_tests branch 3 times, most recently from 830e176 to feff8e1 Compare March 5, 2026 06:26
@jeffsmale90 jeffsmale90 force-pushed the chore/gator_permissions_decoding_adversarial_tests branch from feff8e1 to c7ba989 Compare March 5, 2026 06:29
Copy link
Member

@MoMannn MoMannn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isSubset is now dead code and can be removed

);
}

if (periodAmountBigInt <= 0n) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These can check only if === 0n. There are no negative numbers in solidity so its not possible for them to be less then 0.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true - they're being decoded from hex so there's no way to encode a negative


const EXPECTED_VALUE_LTE_TERMS_BYTELENGTH = 32;

if (getByteLength(valueLteTerms) !== EXPECTED_VALUE_LTE_TERMS_BYTELENGTH) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is redundant. The next if is validating valueLteTerms !== ZERO_32_BYTES which also validates the length.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point!

* const result = matchingRules[0].validateAndDecodePermission(caveats);
* if (result.isValid) { ... result.expiry, result.data ... }
*
* getPermissionRuleMatchingCaveatTypes and getPermissionDataAndExpiry use these rules
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe these functions got removed now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this comment entirely as it's not really relevant with the refactor.

@jeffsmale90 jeffsmale90 requested a review from MoMannn March 5, 2026 18:03
- checks now check value === 0 instead of <= 0 because hex encoding is always >= 0
- remove redundant terms length check
@jeffsmale90 jeffsmale90 force-pushed the chore/gator_permissions_decoding_adversarial_tests branch from be36041 to 41c7393 Compare March 5, 2026 18:06
@jeffsmale90 jeffsmale90 force-pushed the chore/gator_permissions_decoding_adversarial_tests branch from 2d3f8f7 to c6577ca Compare March 5, 2026 21:09
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Copy link

@mj-kiwi mj-kiwi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jeffsmale90 jeffsmale90 enabled auto-merge March 6, 2026 07:19
@jeffsmale90 jeffsmale90 added this pull request to the merge queue Mar 6, 2026
Merged via the queue into main with commit cb37525 Mar 6, 2026
322 checks passed
@jeffsmale90 jeffsmale90 deleted the chore/gator_permissions_decoding_adversarial_tests branch March 6, 2026 07:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants